package com.spark.sniffer.plugin.plugins.interceptor;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;

import com.spark.sniffer.AgentArgs;
import com.spark.sniffer.logging.api.Logger;
import com.spark.sniffer.logging.api.LoggerFactory;
import com.spark.sniffer.plugin.interceptor.enhance.InstanceMethodsAroundInterceptor;
import com.spark.sniffer.plugin.interceptor.enhance.MethodInterceptResult;
import com.spark.sniffer.tracing.Span;
import com.spark.sniffer.tracing.TracingContext;
import com.spark.sniffer.util.ReflectUtil;

// import feign.Request;

public class FeignClientInterceptor implements InstanceMethodsAroundInterceptor {
    private static final Logger log = LoggerFactory.getLogger(FeignClientInterceptor.class);

    @Override
    public void beforeMethod(Object instance, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
        MethodInterceptResult result) throws Throwable {
        // 数据透传处理
        passThroughs(allArguments);

        if (TracingContext.getContext().getEntrance() == null) {
            return;
        }

        // allArguments[0] = feign.Request
        TracingContext.getContext().startSpan(Span.Type.OpenFeign,
            AgentArgs.isSimpleTracing() ? null : ReflectUtil.getMethod(allArguments[0], "url").invoke());
    }

    @Override
    public Object afterMethod(Object instance, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
        Object ret) throws Throwable {
        if (TracingContext.getContext().getEntrance() == null) {
            return ret;
        }

        TracingContext.getContext().stopSpan();
        return ret;
    }

    @Override
    public void handleMethodException(Object instance, Method method, Object[] allArguments, Class<?>[] argumentsTypes,
        Throwable t) {
        TracingContext.getContext().setActiveSpanCode(Span.ERR_CODE);
    }

    /**
     * @param allArguments
     * @throws NoSuchFieldException
     * @throws IllegalAccessException
     */
    private void passThroughs(Object[] allArguments) throws NoSuchFieldException, IllegalAccessException {
        // 将traceid/X-User-Agent/abtest_group通过http header向下游系统进行透传
        // 获取FeignClient的headers属性并修改为可见属性
        Field headersField = allArguments[0].getClass().getDeclaredField("headers");
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(headersField, headersField.getModifiers() & ~Modifier.FINAL);
        headersField.setAccessible(true);

        // 获取header参数
        Map<String, Collection<String>> headers = new LinkedHashMap<String, Collection<String>>();
        headers.putAll(ReflectUtil.getMethod(allArguments[0], "headers").invoke()); // 注意需要保留原有可能存在的header

        // 设置透传数据
        Map<String, String> passThroughMap = TracingContext.getContext().getPassThroughMap();
        if (passThroughMap != null) {
            passThroughMap.entrySet().forEach(x -> {
                headers.put(x.getKey(), Collections.singletonList(x.getValue()));
                log.debug("pass through FeignClient header: {}={}", x.getKey(), x.getValue());
            });
        }

        headersField.set(allArguments[0], Collections.unmodifiableMap(headers));
    }
}
